﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Data;
using Framework.Data.Exceptions;
using Framework.Data.Security;
using Framework.Data.DataManager;
using System.Diagnostics;
//
//Description : Classe de base pour les entités
//Auteur : Marc-Eric Boisvert
//Date Création : 31 mars 2008
//Révisions:
//

namespace Framework.Data.Core
{
    [Serializable()]
    public abstract class BaseEntity : IBaseEntity
    {

        #region "Membres"
        //Liste de champs
        protected Dictionary<string, IField> _fields = new Dictionary<string, IField>();
        //Liste de childrenContainer
        protected Dictionary<string, ChildrenContainer> _childrenContainers = new Dictionary<string, ChildrenContainer>();
        //Liste de childrenContainer

        protected Dictionary<string, AttachedContainer> _attachedContainers = new Dictionary<string, AttachedContainer>();
        //Indicateur si l'enregistrement est nouveau
        protected bool _isNew = true;
        //Indique si l'entité est supprimé
        protected bool _markAsDeleted = false;
        //Indicateur que l'enregistrement est en lecture-seule
        protected bool _readOnly = false;
        //Nom du champ de la clé primaire
        #endregion
        protected string _primaryKeyFieldName = string.Empty;

        #region "Constructeurs"

        /// <summary>
        /// Constructeur
        /// </summary>
        /// <remarks></remarks>
        public BaseEntity()
        {
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        /// <remarks></remarks>
        public BaseEntity(IDbConnection connection = null)
            : this(false, connection)
        {
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        /// <param name="setPrimaryKey">Indique si on doit initialiser la clé primaire sur la construction</param>
        /// <remarks></remarks>
        public BaseEntity(bool setPrimaryKey, IDbConnection connection = null )
        {
            this.InitializeFields();
            this.InitializeContainers();
            if (setPrimaryKey)
            {
                this.SetPrimaryKey(connection);
                this.InitializeData();
            }

        }

        /// <summary>
        /// Constructeur pour le duplicate
        /// </summary>
        /// <param name="entity">L'entité à dupliquer</param>
        /// <remarks></remarks>
        public BaseEntity(IBaseEntity entity, IDbConnection connection = null )
            : this(entity, true, connection)
        {
        }

        /// <summary>
        /// Constructeur pour le duplicate
        /// </summary>
        /// <param name="entity">L'entité à dupliquer</param>
        /// <param name="duplicateChildren">Indique si on doit dupliquer les enfants aussi</param>
        /// <remarks></remarks>
        public BaseEntity(IBaseEntity entity, bool duplicateChildren, IDbConnection connection = null )
            : this()
        {
            IField field = default(IField);
            //Copie chacun des champs
            foreach (string fieldKey in entity.Fields.Keys)
            {
                field = this.Fields[fieldKey];
                if (!((IReadOnly)field).ReadOnly)
                {
                    field.Value = entity.Fields[fieldKey].Value;
                }
            }
            //Set une nouvelle clé primaire
            this.SetPrimaryKey(connection);

            //est-ce que l'on duplique les enfants?
            if (duplicateChildren)
            {
                //On duplique les enfants
                foreach (string containerKey in entity.ChildrenContainers.Keys)
                {
                    entity.ChildrenContainers[containerKey].LoadData(connection);
                    foreach (IBaseEntity child in entity.ChildrenContainers[containerKey].EntityCollection.Values)
                    {
                        this.ChildrenContainers[containerKey].DuplicateEntity(child, connection);
                    }
                    this.ChildrenContainers[containerKey].IsLoaded = true;
                }

                //On duplique les attachements
                foreach (string containerKey in entity.AttachedContainers.Keys)
                {
                    entity.AttachedContainers[containerKey].LoadData(connection);
                    foreach (AttachedEntity child in entity.AttachedContainers[containerKey].EntityCollection.Values)
                    {
                        this.AttachedContainers[containerKey].DuplicateEntity(child.Entity, connection);
                    }
                    this.AttachedContainers[containerKey].IsLoaded = true;
                }
            }
        }

        #endregion

        #region "Méthodes"

        /// <summary>
        /// Procédure d'initialisation des champs de l'entité. 
        /// Cette procédure doit être surchargé dans la classe héritière.
        /// </summary>
        /// <remarks></remarks>
        protected abstract void InitializeFields();

        /// <summary>
        /// Procédure d'initialisation des containers
        /// </summary>
        /// <remarks></remarks>

        protected virtual void InitializeContainers()
        {
        }

        /// <summary>
        /// Procédure d'initialisation des valeurs par défaut des champs
        /// </summary>
        /// <remarks></remarks>

        protected virtual void InitializeData()
        {
        }

        /// <summary>
        /// Initialise la clé primaire
        /// </summary>
        /// <remarks>
        /// Example:
        /// IField field = this.GetPrimaryKeyField();
        /// field.Value = SecurityManager.DataBroker.GetNextID(connection);
        /// field.AcceptChanges();
        /// </remarks>
        protected abstract void SetPrimaryKey(IDbConnection connection = null);


        /// <summary>
        /// Procédure qui permet d'accepter les modifications à un enregistrement
        /// </summary>
        /// <remarks></remarks>
        public void AcceptChanges(bool updateIsNew = true)
        {
            //accepte les chamgement dans les champs
            foreach (IField field in this._fields.Values)
            {
                field.AcceptChanges();
            }
            this.IsNew = !updateIsNew;

            //accepte les changements dans les enfants
            foreach (ChildrenContainer container in this.ChildrenContainers.Values)
            {
                container.AcceptChanges();
            }

            //accepte les changements dans les attachements
            foreach (AttachedContainer container in this.AttachedContainers.Values)
            {
                container.AcceptChanges();
            }
        }

        /// <summary>
        /// Procédure qui permet de rejeter les changements à un enregistrement
        /// </summary>
        /// <remarks></remarks>
        public void RejectChanges()
        {
            foreach (IField field in this._fields.Values)
            {
                field.RejectChanges();
            }
            this.MarkAsDeleted = false;

            //rejette les changements dans les enfants
            foreach (ChildrenContainer container in this.ChildrenContainers.Values)
            {
                container.RejectChanges();
            }

            //rejette les changements dans les attachements
            foreach (AttachedContainer container in this.AttachedContainers.Values)
            {
                container.RejectChanges();
            }
        }

        #endregion

        #region "IBaseEntity"

        public bool IsNew
        {
            get { return this._isNew; }
            set { this._isNew = value; }
        }

        public Dictionary<string, IField> Fields
        {
            get { return this._fields; }
        }

        public virtual object HashCode
        {
            get { return this.GetPrimaryKeyField().OriginalValue; }
        }

        public Dictionary<string, ChildrenContainer> ChildrenContainers
        {
            get { return this._childrenContainers; }
        }

        public Dictionary<string, AttachedContainer> AttachedContainers
        {
            get { return this._attachedContainers; }
        }

        /// <summary>
        /// Function qui permet de dupliquer un entité
        /// </summary>
        /// <returns>IBaseEntity</returns>
        /// <remarks></remarks>
        public IBaseEntity Duplicate(bool duplicateChildren, IDbConnection connection = null)
        {
            return (IBaseEntity)Activator.CreateInstance(this.GetType(), new object[] {
			this,
			duplicateChildren,
			connection
		});
        }

        /// <summary>
        /// Fonction qui permet de rechercher un champs par son nom
        /// </summary>
        /// <param name="fieldName">Nom du champ à retrouver</param>
        /// <returns>IField</returns>
        /// <remarks></remarks>
        public IField GetField(string fieldName)
        {
            Debug.Assert(this._fields.ContainsKey(fieldName.ToUpper()), string.Format("Le champ {0} n'exite pas dans la classe {1}.", fieldName, this.GetType().Name));
            return this._fields[fieldName.ToUpper()];
        }

        /// <summary>
        /// Fonction qui permet de rechercher le champ de la clé primaire
        /// </summary>
        /// <returns>IField</returns>
        /// <remarks></remarks>
        public IField GetPrimaryKeyField()
        {
            if (this._primaryKeyFieldName == string.Empty)
            {
                foreach (IEntityField field in this._fields.Values)
                {
                    if (field.IsPrimaryKey)
                    {
                        this._primaryKeyFieldName = ((IField)field).Name;
                        break; // TODO: might not be correct. Was : Exit For
                    }
                }
            }
            return this.GetField(this._primaryKeyFieldName);
        }

        /// <summary>
        /// Procédure d'ajout d'un champ à la collection de champs
        /// </summary>
        /// <param name="key">Clé</param>
        /// <param name="field">Champ à ajouter</param>
        /// <remarks></remarks>
        public void AddField(string key, IField field)
        {
            this._fields.Add(key.ToUpper(), field);
        }

        /// <summary>
        /// Procédure d'initialisation des champs via un IDataReader
        /// </summary>
        /// <param name="dataReader">DataReader qui contient les données</param>
        /// <remarks></remarks>
        public virtual void InitializeData(System.Data.IDataReader dataReader)
        {
            IField field = default(IField);
            for (int i = 0; i <= dataReader.FieldCount - 1; i++)
            {
                field = this.GetField(dataReader.GetName(i));
                field.InitializeValue(dataReader.GetValue(i));
            }
            this.IsNew = false;
        }

        /// <summary>
        /// Procédure d'initialisation des champs via un dataRow
        /// </summary>
        /// <param name="dataRow">dataRow qui contient les données</param>
        /// <remarks></remarks>
        public virtual void InitializeData(System.Data.DataColumnCollection dataColumns, System.Data.DataRow dataRow)
        {
            IField field = default(IField);
            foreach (DataColumn col in dataColumns)
            {
                field = this.GetField(col.ColumnName);
                field.InitializeValue(dataRow[col.ColumnName]);
            }
            this.IsNew = false;
        }

        /// <summary>
        /// Fonction qui permet de valider un entité
        /// </summary>
        /// <returns>List(Of AppException)</returns>
        /// <remarks></remarks>
        public virtual List<AppException> ValidateRules()
        {
            return new List<AppException>();
        }

        /// <summary>
        /// Fonction qui valide si on peut supprimer l'entité
        /// </summary>
        /// <param name="deleteException">exception sur le delete</param>
        /// <returns>Boolean</returns>
        /// <remarks></remarks>
        public virtual bool CanDelete(ref AppException deleteException)
        {
            return true;
        }

        /// <summary>
        /// Fonction qui retourne le type de l'entité
        /// </summary>
        /// <returns>System.Type</returns>
        /// <remarks></remarks>
        public new System.Type GetType()
        {
            return base.GetType();
        }

        /// <summary>
        /// Fonction qui permet d'afficher l'instance de cette classe sous forme de chaîne de caractère
        /// </summary>
        /// <returns>String</returns>
        /// <remarks></remarks>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Entity type = [ ").Append(this.GetType().Name).Append(" ]").Append(Environment.NewLine);

            foreach (IField field in this._fields.Values)
            {
                sb.Append(field.Name).Append(" Value=[ ").Append(field.ToString()).Append(" ]");
                if ((field.OriginalValue != null))
                {
                    sb.Append(" Original Value=[ ").Append(field.OriginalValue.ToString()).Append(" ]");
                    sb.Append(" Display Text=[ ").Append(((IListField)field).DisplayText).Append(" ]");
                }
                sb.Append(Environment.NewLine);
            }

            sb.Append("IsNew=[ ").Append(this.IsNew.ToString()).Append(" ]").Append(Environment.NewLine);
            sb.Append("IsDirty=[ ").Append(this.IsDirty().ToString()).Append(" ]").Append(Environment.NewLine);
            sb.Append("ReadOnly=[ ").Append(this.ReadOnly.ToString()).Append(" ]").Append(Environment.NewLine);

            return sb.ToString();
        }

        /// <summary>
        /// Fonction qui retourne le tri par défaut d'un entité
        /// </summary>
        /// <returns>String</returns>
        /// <remarks></remarks>
        public virtual string GetDefaultSortOrder()
        {
            return null;
        }
        #endregion

        #region "IEditableEntity"

        public bool MarkAsDeleted
        {
            get { return this._markAsDeleted; }
            set { this._markAsDeleted = value; }
        }
        /// <summary>
        /// Fonction qui permet de savoir si l'entité à été modifié
        /// </summary>
        /// <returns>Boolean</returns>
        /// <remarks></remarks>
        public virtual bool IsDirty(bool checkFieldOnly = false)
        {
            //Est-ce un nouvel entité ou un entité à supprimer
            if (!checkFieldOnly)
            {
                if (this._isNew | this._markAsDeleted)
                {
                    return true;
                }
            }

            //On vérifie si un champ est dirty
            foreach (IField field in this._fields.Values)
            {
                if ((!(field is IForeignField)) && (!(field is IForeignFieldMultiTable)))
                {
                    if (field.IsDirty)
                    {
                        return true;
                    }
                }
            }

            //On vérifie si un enfant est dirty
            foreach (ChildrenContainer container in this.ChildrenContainers.Values)
            {
                foreach (IBaseEntity entity in container.EntityCollection.Values)
                {
                    if (entity.IsDirty())
                    {
                        return true;
                    }
                }
            }

            //On vérifie si un attachement est dirty
            foreach (AttachedContainer container in this.AttachedContainers.Values)
            {
                foreach (IBaseEntity entity in container.EntityCollection.Values)
                {
                    if (entity.IsDirty())
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Fonction qui permet de canceller les changements à l'entité
        /// </summary>
        /// <returns>Boolean</returns>
        /// <remarks></remarks>
        public virtual bool Cancel()
        {
            this.RejectChanges();
            return true;
        }

        /// <summary>
        /// Fonction qui permet de supprimer cet entité
        /// </summary>
        /// <returns>Boolean</returns>
        /// <remarks></remarks>
        public virtual bool Delete(IDbConnection connection = null)
        {
            EntityCollection<object, IBaseEntity> collection = new EntityCollection<object, IBaseEntity>();
            this._markAsDeleted = true;
            collection.Add(this.GetPrimaryKeyField().Name, this);
            return DataEntityManager.Instance.Delete(collection, connection);
        }

        /// <summary>
        /// Fonction qui permet de sauvegarder cet entité
        /// </summary>
        /// <returns>Boolean</returns>
        /// <remarks></remarks>
        public virtual bool Save(IDbConnection connection = null)
        {
            EntityCollection<object, IBaseEntity> collection = new EntityCollection<object, IBaseEntity>();
            collection.Add(this.GetPrimaryKeyField().OriginalValue, this);
            bool result = DataEntityManager.Instance.Save(collection, connection);
            if (result)
            {
                this.AcceptChanges();
            }
            return result;
        }

        /// <summary>
        /// Fonction qui permet de sauvegarder cet entité
        /// </summary>
        /// <param name="transaction">Transaction</param>
        /// <returns>Boolean</returns>
        /// <remarks></remarks>
        public virtual bool Save(IDbTransaction transaction, IDbConnection connection = null)
        {
            EntityCollection<object, IBaseEntity> collection = new EntityCollection<object, IBaseEntity>();
            collection.Add(this.GetPrimaryKeyField().OriginalValue, this);
            bool result = DataEntityManager.Instance.Save(collection, transaction, connection);
            if (result)
            {
                this.AcceptChanges();
            }
            return result;
        }

        /// <summary>
        /// Retourne un entité
        /// </summary>
        /// <param name="entityType">Type de l'entité</param>
        /// <param name="id">Id de l'entité</param>
        /// <returns>IBaseEntity</returns>
        public static IBaseEntity GetEntity(Type entityType, long id, IDbConnection connection = null)
        {
            return DataEntityManager.Instance.GetEntity(entityType, id, connection);
        }

        /// <summary>
        /// Fonction qui retourne un entité selon les critères spécifiés
        /// </summary>
        /// <param name="entityType">Type de l'entité</param>
        /// <param name="filters">Filtres</param>
        /// <returns>IBaseEntity</returns>
        /// <remarks></remarks>
        public static IBaseEntity GetEntity(Type entityType, List<string> filters, IDbConnection connection = null)
        {
            IEntityCollection<object, IBaseEntity> entities = default(IEntityCollection<object, IBaseEntity>);
            entities = BaseEntity.GetEntityList(entityType, filters, null, connection);
            if ((entities != null) && (entities.Count > 0))
            {
                return entities.ToArrayList()[0];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Retourne la liste complète des enregistrements
        /// </summary>
        /// <param name="entityType">Type de l'entité</param>
        /// <returns>Une Collection de BaseEntity</returns>
        public static IEntityCollection<object, IBaseEntity> GetEntityList(Type entityType, IDbConnection connection = null, Int32 fetchRowCount = 250)
        {
            IBaseEntity entity = (IBaseEntity)Activator.CreateInstance(entityType, new object[] { connection });
            return BaseEntity.GetEntityList(entityType, new List<string>(), entity.GetDefaultSortOrder(), connection, fetchRowCount);
        }

        /// <summary>
        /// Retourne la liste complète des enregistrements
        /// </summary>
        /// <param name="entityType">Type de l'entité</param>
        /// <param name="filters">Liste de filtre</param>
        /// <returns>Une Collection de BaseEntity</returns>
        public static IEntityCollection<object, IBaseEntity> GetEntityList(Type entityType, List<string> filters, string sortOrder, IDbConnection connection = null, Int32 fetchRowCount = 250, Int32 rowsPerPage = 0, Int32 pageIndex = 0)
        {
            return DataEntityManager.Instance.GetEntityList(entityType, filters, sortOrder, connection, fetchRowCount, rowsPerPage, pageIndex);
        }

        /// <summary>
        /// Retourne la liste complète des enregistrements
        /// </summary>
        /// <param name="entityType">Type de l'entité</param>
        /// <param name="sql">Select sql</param>
        /// <returns>Une Collection de BaseEntity</returns>
        public static IEntityCollection<object, IBaseEntity> GetEntityList(Type entityType, string sql, IDbConnection connection = null, Int32 fetchRowCount = 250)
        {
            return DataEntityManager.Instance.GetEntityList(entityType, sql, connection, fetchRowCount);
        }

        /// <summary>
        /// Retourne la liste complète des enregistrements
        /// </summary>
        /// <param name="entityType">Type de l'entité</param>
        /// <param name="filters">Liste de filtre</param>
        /// <returns>Une Collection de BaseEntity</returns>
        public static IEntityCollection<object, IBaseEntity> GetAttachedEntityList(Type entityType, IBaseEntity parentEntity, string linkSchemaName, string linkTableName, List<string> filters, string sortOrder, IDbConnection connection = null, Int32 fetchRowCount = 250, Int32 rowsPerPage = 0, Int32 pageIndex = 0)
        {
            return DataEntityManager.Instance.GetAttachedEntityList(entityType, parentEntity, linkSchemaName, linkTableName, filters, sortOrder, connection, fetchRowCount, rowsPerPage, pageIndex);
        }


        #endregion

        #region "IReadOnly"
        public bool ReadOnly
        {
            get { return this._readOnly; }
            set { this._readOnly = value; }
        }
        #endregion

    }
}